package ecnu.ecgbackend.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import ecnu.ecgbackend.dao.*;
import ecnu.ecgbackend.entity.*;
import ecnu.ecgbackend.service.DiagnosisFileService;
import ecnu.ecgbackend.utils.MD5Calculator;
import ecnu.ecgbackend.utils.Result;
import ecnu.ecgbackend.vo.PatientArtificialVo;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class DiagnosisFileServiceImpl implements DiagnosisFileService {
    private static final String DATA_DIR="src/main/resources/temp/";

    @Resource
    RawDataFragmentMapper rawDataFragmentMapper;
    @Resource
    RawDataMapper rawDataMapper;
    @Resource
    ArtificialDiagnosisMapper artificialDiagnosisMapper;
    @Resource
    UserMapper userMapper;
    @Resource
    DeveloperMapper developerMapper;
    @Resource
    PatientMapper patientMapper;

    public boolean isFragmentExist(String fragmentId){
        if(rawDataFragmentMapper.selectOne(new QueryWrapper<RawDataFragment>().eq("fragment_id",fragmentId))!=null){
            return true;
        }
        return false;
    }
    public List<RawDataFragment> getFragments(String rawDataId){
        QueryWrapper<RawDataFragment> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("raw_data_id",rawDataId).orderByAsc("fragment_id");
        return rawDataFragmentMapper.selectList(queryWrapper);
    }
    public boolean deleteFragments(String rawDataId){
        QueryWrapper<RawDataFragment> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("raw_data_id",rawDataId);
        if(rawDataFragmentMapper.delete(queryWrapper)>0){
            return true;
        }
        return false;
    }
    public boolean saveFragment(String fragmentId, Integer interval, Integer length,
                                byte[] bytes, String rawDataId, Integer rawFileOffset) {
        RawDataFragment rawDataFragment=new RawDataFragment(
                fragmentId,
                interval,
                length,
                bytes,
                rawDataId,
                rawFileOffset,
                new Timestamp((new Date()).getTime()),
                null,
                null,
                null
        );
        if(rawDataFragmentMapper.insert(rawDataFragment)>0){
            return true;
        }
        return false;
    }
    public boolean isRawDataExist(String filePath) {
        String md5= MD5Calculator.calculateMD5(filePath);
        QueryWrapper<RawData> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("md5",md5);
        if(rawDataMapper.selectOne(queryWrapper)!=null){
            return true;
        }
        return false;
    }
    public boolean isFileExist(String fileName){
        String folderPath="src/main/resources/temp";
        File folder=new File(folderPath);
        if(folder.exists() && folder.isDirectory()){
            List<String> fileNames=Arrays.asList(folder.listFiles()).stream().map(item->item.getName()).collect(Collectors.toList());
            if(fileNames.contains(fileName)){
                return true;
            }
            return false;
        }
        return false;
    }
    public boolean saveRawData(String rawDataId, String category, String filePathRemote, String filePathLocal, Timestamp updateDate, String patientId, String md5) {
        int fragmentCnt=rawDataFragmentMapper.selectList(new QueryWrapper<RawDataFragment>().eq("raw_data_id",rawDataId)).size();
        RawData rawData=new RawData(
                rawDataId,
                filePathRemote,
                filePathLocal,
                updateDate,
                patientId,
                md5,
                "[\"Ⅰ\",\"Ⅱ\",\"Ⅲ\"]",
                250,
                null,
                null,
                null,
                null,
                fragmentCnt,
                category,
                null,
                "nostart"
        );
        if(rawDataMapper.insert(rawData)>0){
            return true;
        }
        return false;
    }

        @Override
    public void saveFragmentsToFile(String filePath, List<RawDataFragment> rawDataFragments) throws IOException{
        FileOutputStream fos=new FileOutputStream(new File(filePath),true);
        rawDataFragments.stream().forEach(element->{
            try {
                fos.write(element.getContent());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        fos.close();
    }

    public void deleteFile(String filePath) {
        File fileToDelete = new File(filePath);
        fileToDelete.delete();
    }
    //心电图文件分片上传
    @Override
    public Result uploadChunk(Integer index, String fileName, Integer interval, Integer length, MultipartFile multipartFile) {
        try {
            User user= (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String rawDataId=user.getId()+"_"+fileName;
            String fragmentId=user.getId()+"_"+fileName+"_chunk_"+index;
            Integer rawFileOffset=index*10*1024*1024;
            if(isFragmentExist(fragmentId) && isFileExist(rawDataId)){//数据库中有分片且文件夹中也有文件
                return Result.error().message("文件重复上传");
            }else if(isFragmentExist(fragmentId) && !isFileExist(rawDataId)){//数据库中有分片，但文件夹中没文件，这次上传的分片就取消
                return Result.ok();
            }
            if(saveFragment(fragmentId, interval, length, multipartFile.getBytes(), rawDataId, rawFileOffset)){
                return Result.ok("分片上传成功！");
            }
            return Result.error().message("分片上传失败");
        }catch (Exception e){
            e.printStackTrace();
            return Result.error().message("分片上传失败");
        }
    }
    //心电图文件分片合并
    @Override
    public Result mergeChunk(String fileName, String category) throws IOException {
        User user= (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String rawDataId=user.getId()+"_"+fileName;
        List<RawDataFragment> rawDataFragments=getFragments(rawDataId);
        //写入文件
        String filePath=DATA_DIR+rawDataId;
        saveFragmentsToFile(filePath,rawDataFragments);
        //校验文件内容是否相同
//        if(isRawDataExist(filePath)){
//            deleteFragments(rawDataId);
//            deleteFile(filePath);
//            return Result.error().message("文件重复上传！");
//        }
        if(saveRawData(rawDataId,category,filePath+"_remote",filePath,new Timestamp((new Date()).getTime()),user.getId().toString(), MD5Calculator.calculateMD5(filePath))){
            return Result.ok("文件上传成功！");
        }
        return Result.error().message("文件上传失败");
    }
    //心电图文件分片展示
    @Override
    public Result getDataSegment(String rawDataId, Integer startTime) throws IOException {
        List<RawData> rawDatas=rawDataMapper.selectList(new QueryWrapper<RawData>().eq("raw_data_id",rawDataId));
        if(rawDatas.size()>0){
            String filePath=rawDatas.get(0).getFilePathLocal();
            RandomAccessFile randomAccessFile=new RandomAccessFile(filePath, "r");
            FileChannel fileChannel=randomAccessFile.getChannel();
            int start=startTime*2*3*250;
            int end=Math.min(start+45000,(int) fileChannel.size());
            if(end<=start){
                return Result.error().message("超出该心电图的时间范围");
            }
            MappedByteBuffer mappedByteBuffer=fileChannel.map(FileChannel.MapMode.READ_ONLY,start,end-start);
            byte[] fileData=new byte[end-start];
            mappedByteBuffer.get(fileData);
            fileChannel.close();
            int fileLength=fileData.length/2;
            short[] dataInt16=new short[fileLength];
            ByteBuffer buffer=ByteBuffer.wrap(fileData);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            for(int i=0;i<fileLength;i++){
                dataInt16[i]=buffer.getShort();
            }
            int ecgLength=fileLength/3*3;
            dataInt16= Arrays.copyOf(dataInt16,ecgLength);
            double[][] reDataInt16=new double[3][ecgLength/3];
            for(int i=0;i<ecgLength;i++){
                reDataInt16[i%3][i/3]=Math.max(-300,Math.min(300,dataInt16[i]*0.0024));
            }
            return Result.ok(reDataInt16);
        }
        return Result.error();
    }

    @Override
    public Result getDataSample(String fileName, Integer index) throws IOException {
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String rawDataId = user.getId() + "_" + fileName;
        List<RawData> rawDatas = rawDataMapper.selectList(new QueryWrapper<RawData>().eq("raw_data_id", rawDataId));
        if (rawDatas.size() > 0) {
            String filePath = rawDatas.get(0).getFilePathLocal();
            RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r");
            FileChannel fileChannel = randomAccessFile.getChannel();
            int start = index * 2 * 3 - 3000;
            int end = index * 2 * 3 + 3000;
            if (end > fileChannel.size()) {
                return Result.error().message("超出该心电图的时间范围");
            }
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, start, end - start);
            byte[] fileData = new byte[end - start];
            mappedByteBuffer.get(fileData);
            fileChannel.close();
            int fileLength = fileData.length / 2;
            short[] dataInt16 = new short[fileLength];
            ByteBuffer buffer = ByteBuffer.wrap(fileData);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            for (int i = 0; i < fileLength; i++) {
                dataInt16[i] = buffer.getShort();
            }
            int ecgLength=fileLength/3*3;
            dataInt16=Arrays.copyOf(dataInt16,ecgLength);
            double[][] reDataInt16=new double[3][ecgLength/3];
            for(int i=0;i<ecgLength;i++){
                reDataInt16[i%3][i/3]=Math.max(-300,Math.min(300,dataInt16[i]*0.0024));
            }
            return Result.ok(reDataInt16);
        }
        return Result.error();
    }

    @Override
    public List<String> getFileList() {
        User user= (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        List<RawData> rawDataList=rawDataMapper.selectList(new QueryWrapper<RawData>().eq("patient_id",user.getId()));
        List<String> files=rawDataList.stream().map(item->item.getRawDataId().split("_")[1]).collect(Collectors.toList());
        return files;
    }

    @Override
    public Map<String,Object> getFileInfo(String rawDataId) {
        User _user= (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Map<String,Object> result=new HashMap<>();
        RawData rawData=rawDataMapper.selectOne(new QueryWrapper<RawData>().eq("raw_data_id",rawDataId));
        User user=userMapper.selectById(rawData.getPatientId());
        ArtificialDiagnosis artificialDiagnosis=artificialDiagnosisMapper.selectOne(
                new QueryWrapper<ArtificialDiagnosis>().eq("patient_id",rawData.getPatientId())
                        .eq("doctor_id",_user.getId())
                        .eq("raw_data_id",rawDataId)
        );
        Object role=null;
        if(user.getRole().equals("patient")){
            role=patientMapper.selectById(user.getId());
        }else if(user.getRole().equals("developer")){
            role=developerMapper.selectById(user.getId());
        }
        result.put("user",user);
        result.put("role",role);
        result.put("rawData",rawData);
        result.put("artificialDiagnosis",artificialDiagnosis);
        return result;
    }


}
